program Movement;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  SampleCommon,
  DIrr in '..\..\DSource\DIrr.pas',
  DIrr.GUI in '..\..\DSource\DIrr.GUI.pas',
  DIrr.Color in '..\..\DSource\DIrr.Color.pas',
  DIrr.Rect in '..\..\DSource\DIrr.Rect.pas',
  DIrr.Event in '..\..\DSource\DIrr.Event.pas',
  DIrr.Vector2D in '..\..\DSource\DIrr.Vector2D.pas',
  DIrr.Vector3D in '..\..\DSource\DIrr.Vector3D.pas',
  DIrr.Video in '..\..\DSource\DIrr.Video.pas',
  DIrr.Base in '..\..\DSource\DIrr.Base.pas',
  DIrr.Types in '..\..\DSource\DIrr.Types.pas',
  DIrr.Scene in '..\..\DSource\DIrr.Scene.pas',
  DIrr.Structs in '..\..\DSource\DIrr.Structs.pas',
  DIrr.Aabbox3D in '..\..\DSource\DIrr.Aabbox3D.pas',
  DIrr.Matrix4 in '..\..\DSource\DIrr.Matrix4.pas',
  DIrr.Texture in '..\..\DSource\DIrr.Texture.pas',
  DIrr.Collections in '..\..\DSource\DIrr.Collections.pas';

type
  TMyEventReceiver = class(TEventReceiver)
  private
    FKeyIsDown: array[EKEY_CODE] of Boolean;
  public
    constructor Create;
  public
    function OnEvent(const Event: SEvent): Boolean; override;
    function IsKeyDown(KeyCode: EKEY_CODE): Boolean;
  end;

{ TMyEventReceiver }

constructor TMyEventReceiver.Create;
var
  i: EKEY_CODE;
begin
  for i := Low(EKEY_CODE) to High(EKEY_CODE) do
    FKeyIsDown[i] := False;
end;

function TMyEventReceiver.IsKeyDown(KeyCode: EKEY_CODE): Boolean;
begin
  Result := FKeyIsDown[KeyCode];
end;

function TMyEventReceiver.OnEvent(const Event: SEvent): Boolean;
begin
  // Remember whether each key is down or up
  if Event.EventType = EET_KEY_INPUT_EVENT then
    FKeyIsDown[Event.KeyInput.Key] := (cksPressed in Event.KeyInput.ComboKeys);

  Result := False;
end;

const
  MOVEMENT_SPEED = 5.0;

var
  DriverType: E_DRIVER_TYPE;
  Receiver: TMyEventReceiver;
  Device: TIrrlichtDevice;
  Driver: TVideoDriver;
  Smgr: TSceneManager;
  Node, n: TSceneNode;
  Anim: TSceneNodeAnimator;
  Anms: TAnimatedMeshSceneNode;
  Diagnostics: TGUIStaticText;
  LastFPS, FPS: Integer;
  TheTime, NowTime: U32;
  FrameDeltaTime: F32;
  NodePosition: TVector3DF;
  TmpStr: UnicodeString;    //guie:    TGUIEnvironment;
begin
  try
    // ask user for driver
    DriverType := DriverChoiceConsole;
    if DriverType = EDT_COUNT then
    begin
      ExitCode := 1;
      Exit;
    end;

    // create device
    Receiver := TMyEventReceiver.Create;

    Device := createDevice(DriverType, [640, 480], 16, False, False, False, Receiver);
    if Device = nil then
    begin
      ExitCode := 1;   // could not create selected driver.
      Exit;
    end;

    Driver := Device.GetVideoDriver;
    Smgr := Device.GetSceneManager;

    (*
    Create the node which will be moved with the WSAD keys. We create a
    sphere node, which is a built-in geometry primitive. We place the node
    at (0,0,30) and assign a texture to it to let it look a little bit more
    interesting. Because we have no dynamic lights in this scene we disable
    lighting for each model (otherwise the models would be black).
    *)
    Node := Smgr.AddSphereSceneNode;
    if Assigned(Node) then
    begin
      Node.SetPosition(TVector3DF.Create(0, 0, 30));
      Node.SetMaterialTexture(0, Driver.GetTexture('..\..\Media\wall.bmp'));
      Node.SetMaterialFlag(EMF_LIGHTING, False);
    end;

    (*
    Now we create another node, movable using a scene node animator. Scene
    node animators modify scene nodes and can be attached to any scene node
    like mesh scene nodes, billboards, lights and even camera scene nodes.
    Scene node animators are not only able to modify the position of a
    scene node, they can also animate the textures of an object for
    example. We create a cube scene node and attach a 'fly circle' scene
    node animator to it, letting this node fly around our sphere scene node.
    *)
    n := Smgr.AddCubeSceneNode;
    if Assigned(n) then
    begin
      n.SetMaterialTexture(0, Driver.GetTexture('..\..\Media\t351sml.jpg'));
      n.SetMaterialFlag(EMF_LIGHTING, False);
      Anim := Smgr.CreateFlyCircleAnimator(TVector3DF.Create(0, 0, 30), 20);
      if Assigned(Anim) then
      begin
        n.AddAnimator(Anim);
        Anim.Drop;
      end;
    end;

    (*
    The last scene node we add to show possibilities of scene node animators is
    a b3d model, which uses a 'fly straight' animator to run between to points.
    *)
    Anms := Smgr.AddAnimatedMeshSceneNode(Smgr.GetMesh('..\..\Media\ninja.b3d'));

    if Assigned(Anms) then
    begin
      Anim := Smgr.CreateFlyStraightAnimator(TVector3DF.Create(100, 0, 60),
        TVector3DF.Create(-100, 0, 60), 3500, True);
      if Assigned(Anim) then
      begin
        Anms.AddAnimator(Anim);
        Anim.Drop;
      end;

      (*
      To make the model look right we disable lighting, set the
      frames between which the animation should loop, rotate the
      model around 180 degrees, and adjust the animation speed and
      the texture. To set the right animation (frames and speed), we
      would also be able to just call
      "anms->setMD2Animation(scene::EMAT_RUN)" for the 'run'
      animation instead of "setFrameLoop" and "setAnimationSpeed",
      but this only works with MD2 animations, and so you know how to
      start other animations. But a good advice is to not use
      hardcoded frame-numbers...
      *)
      Anms.SetMaterialFlag(EMF_LIGHTING, False);

      Anms.SetFrameLoop(0, 13);
      Anms.SetAnimationSpeed(15);
      //Anms.setMD2Animation(EMAT_RUN);

      Anms.SetScale(TVector3DF.Create(2, 2, 2));
      Anms.SetRotation(TVector3DF.Create(0, -90, 0));
      //Anms.SetMaterialTexture(0, Driver.GetTexture('..\media\sydney.bmp'));
    end;


    (*
    To be able to look at and move around in this scene, we create a first
    person shooter style camera and make the mouse cursor invisible.
    *)
    Smgr.AddCameraSceneNodeFPS;
    Device.GetCursorControl.SetVisible(False);

    (*
    Add a colorful irrlicht logo
    *)
    Device.GetGUIEnvironment.AddImage
    (
      Driver.GetTexture('..\..\media\irrlichtlogoalpha2.tga'),
      TVector2D.Create(10, 20)
    );

    Diagnostics := Device.GetGUIEnvironment.AddStaticText('Just a test',
      TIrrRect.Create(10, 10, 400, 20));
    Diagnostics.SetOverrideColor(TIrrColor.Create(255, 255, 255, 0));

    LastFPS := -1;
    theTime := Device.GetTimer.GetTime;

    while (Device.Run) do
    begin
      NowTime := Device.GetTimer.GetTime;
      FrameDeltaTime := (NowTime - TheTime) / 1000;
      TheTime := NowTime;

      NodePosition := Node.GetPosition^;

      if Receiver.IsKeyDown(KEY_KEY_W) then
        NodePosition.Y := NodePosition.Y + MOVEMENT_SPEED * FrameDeltaTime
      else if Receiver.IsKeyDown(KEY_KEY_S) then
        NodePosition.Y := NodePosition.Y - MOVEMENT_SPEED * FrameDeltaTime
      else if Receiver.IsKeyDown(KEY_KEY_A) then
        NodePosition.X := NodePosition.X - MOVEMENT_SPEED * FrameDeltaTime
      else if Receiver.IsKeyDown(KEY_KEY_D) then
        NodePosition.X := NodePosition.X + MOVEMENT_SPEED * FrameDeltaTime;

      Node.SetPosition(NodePosition);

      Driver.BeginScene(True, True, [255, 113, 113, 133]);

      Smgr.DrawAll;
      Device.GetGUIEnvironment.DrawAll;

      Driver.EndScene;

      FPS := Driver.GetFPS;

      if LastFPS <> FPS then
      begin
        TmpStr := 'Movement Example - Irrlicht Engine [';
        TmpStr := TmpStr + Driver.GetName + '] fps:' + IntToStr(FPS);

        Device.SetWindowCaption(PChar(TmpStr));
        LastFPS := FPS;
      end;
    end;

    Receiver.Free;

    Device.Drop;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
